import torch
from torch import nn

from models.MLLA import MLLAttention


# from xformers.components import EncoderLayer


# TODO: 完整版轻量级+医学特征融入
class lwCET(nn.Module):
    # add_fea 是否需要融入医学特征
    # Ablation不为None，则为进行消融实验，A-传统卷积，B传统注意力机制
    def __init__(self, configs, hparams, add_fea=None, ablation=None):
        super(lwCET, self).__init__()
        self.ablation = ablation
        self.convType = DepthwiseSeparableConv1d
        self.transformer_encoder = nn.Sequential(
            *[MLLAttention(128) for _ in range(3)]  # 列表推导式创建三个独立实例
        )
        if ablation is not None:
            if 'A' in ablation:
                self.convType = nn.Conv1d
            if 'B' in ablation:
                self.encoder_layer = nn.TransformerEncoderLayer(d_model=configs.trans_dim, nhead=configs.num_heads,
                                                                batch_first=True)
                self.transformer_encoder = nn.TransformerEncoder(self.encoder_layer, num_layers=3)
            # if 'C' in ablation:
        # self.encoder_layer = EncoderLayer(
        #     dim_model=configs.trans_dim,  # 模型维度
        #     nhead=configs.num_heads,  # 注意力头数
        #     batch_first=True,  # 批次维度在前
        #     attention='linear'  # 关键参数：指定使用线性注意力
        # )
        # self.transformer_encoder = nn.TransformerEncoder(self.encoder_layer, num_layers=3)
        filter_sizes = [5, 9, 11]
        self.conv1 = self.convType(configs.input_channels, configs.mid_channels, kernel_size=filter_sizes[0],
                                   stride=configs.stride, bias=False, padding=(filter_sizes[0] // 2))
        self.conv2 = self.convType(configs.input_channels, configs.mid_channels, kernel_size=filter_sizes[1],
                                   stride=configs.stride, bias=False, padding=(filter_sizes[1] // 2))
        self.conv3 = self.convType(configs.input_channels, configs.mid_channels, kernel_size=filter_sizes[2],
                                   stride=configs.stride, bias=False, padding=(filter_sizes[2] // 2))

        self.bn = nn.BatchNorm1d(configs.mid_channels)
        self.relu = nn.ReLU()
        self.mp = nn.MaxPool1d(kernel_size=2, stride=2, padding=1)
        self.do = nn.Dropout(configs.dropout)

        self.conv_block2 = nn.Sequential(
            self.convType(configs.mid_channels, configs.mid_channels * 2, kernel_size=8, stride=1,
                          bias=False, padding=4),
            nn.BatchNorm1d(configs.mid_channels * 2),
            nn.ReLU(),
            nn.MaxPool1d(kernel_size=2, stride=2, padding=1)
        )

        self.conv_block3 = nn.Sequential(
            self.convType(configs.mid_channels * 2, configs.final_out_channels, kernel_size=8, stride=1,
                          bias=False,
                          padding=4),
            nn.BatchNorm1d(configs.final_out_channels),
            nn.ReLU(),
            nn.MaxPool1d(kernel_size=2, stride=2, padding=1),
        )

        self.inplanes = 128
        self.crm = self._make_layer(SEBasicBlock, 128, 3)

        self.aap = nn.AdaptiveAvgPool1d(1)
        if add_fea:
            self.add_fea = True
            self.gate_fc = nn.Linear(180, 180)
            self.clf1 = nn.Linear(180, 128)
            self.clf2 = nn.Linear(128, 32)
            self.clf3 = nn.Linear(32, configs.num_classes)

            # self.gate_fc = nn.Linear(52, 128)
            # self.fusion_layer = GatedAttentionFusion(hparams["feature_dim"], hparams["feature_dim"])
            # self.clf = nn.Linear(hparams["feature_dim"], configs.num_classes)
        else:
            self.add_fea = False
            self.clf = nn.Linear(hparams["feature_dim"], configs.num_classes)

    def _make_layer(self, block, planes, blocks, stride=1):  # makes residual SE block
        downsample = None
        if stride != 1 or self.inplanes != planes * block.expansion:
            downsample = nn.Sequential(
                self.convType(self.inplanes, planes * block.expansion,
                              kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm1d(planes * block.expansion),
            )

        layers = []
        layers.append(block(self.inplanes, planes, self.convType, stride, downsample))
        self.inplanes = planes * block.expansion
        for i in range(1, blocks):
            layers.append(block(self.inplanes, planes, self.convType))

        return nn.Sequential(*layers)

    def forward(self, x_in, feature):

        # 多尺度卷积
        # 三个独立的1D卷积层 (conv1, conv2, conv3) 并行处理输入信号
        #  使用不同的卷积核（5, 9, 11） ，捕捉不同时间尺度上的特征——小核关注局部细节，大核捕获更全局的上下文信息
        x1 = self.conv1(x_in)
        x2 = self.conv2(x_in)
        x3 = self.conv3(x_in)
        #  提取的特征在指定维度上被堆叠并求均值，实现多尺度特征的融合
        x_concat = torch.mean(torch.stack([x1, x2, x3], 2), 2)
        #  批归一化 (bn)、ReLU激活、最大池化 (mp) 和Dropout (do) 层
        #  卷积特征压缩与正则化流程，旨在减少计算量、增强泛化能力并保留最显著的特征
        x_concat = self.do(self.mp(self.relu(self.bn(x_concat))))

        x = self.conv_block2(x_concat)
        x = self.conv_block3(x)

        # 通道重校准模块
        # 融合后的特征会通过一个由三个 SEBasicBlock 组成的通道重校准模块 (CRM)
        x = self.crm(x)

        # Bi-directional Transformer
        # 经过CNN和SE模块提取的丰富特征，被送入双向Transformer编码器来捕捉强大的时序依赖关系。
        # zeros_to_add = torch.zeros(x.shape[0], x.shape[1], 9, dtype=x.dtype, device=x.device)
        # x = torch.cat([x, zeros_to_add], dim=2)
        if self.ablation is None or 'A' in self.ablation:
            x = x.reshape(x.shape[0], x.shape[1], 6, 6)
        x1 = self.transformer_encoder(x)
        x2 = self.transformer_encoder(torch.flip(x, [2]))
        # 先将特征送入Transformer编码器得到输出 x1，再将特征序列反转后送入另一个结构相同的Transformer编码器得到 x2，最后将两者相加。这等效于同时考虑了每个时间点的过去未来的上下文信息，形成了双向编码（Bi-directional Transformer）
        x = x1 + x2
        if self.ablation is None or 'A' in self.ablation:
            x = x.reshape(x.shape[0], x.shape[1], 36)
        x = x[:, :, :-1]
        # 序列特征经过全局平均池化被压缩为一个固定长度的向量，并通过一个全连接层 (clf) 输出最终的分类结果。
        x = self.aap(x)
        # 128 128
        x_flat = x.reshape(x.shape[0], -1)
        if self.add_fea:
            # 融合：拼接两个分支的特征，然后计算门控系数
            # 128 180
            fused = torch.cat([feature, x_flat], dim=1)  # [batch, cnn_channels+embed_dim]
            gate = torch.sigmoid(self.gate_fc(fused))  # 门控系数，形状同 fused
            fused_feature = gate * fused  # 简单使用门控系数对特征加权
            x_out = self.clf1(fused_feature)
            x_out = self.clf2(x_out)
            x_out = self.clf3(x_out)
            # fused = self.gate_fc(feature)
            # x_flat1 = self.fusion_layer(fused, x_flat)
            # x_out = self.clf(x_flat1)
        else:
            x_out = self.clf(x_flat)
        return x_out


class SELayer(nn.Module):
    def __init__(self, channel, reduction=4):
        super(SELayer, self).__init__()
        self.avg_pool = nn.AdaptiveAvgPool1d(1)
        self.fc = nn.Sequential(
            nn.Linear(channel, channel // reduction, bias=False),
            nn.ReLU(inplace=True),
            nn.Linear(channel // reduction, channel, bias=False),
            nn.Sigmoid()
        )

    def forward(self, x):
        b, c, _ = x.size()
        y = self.avg_pool(x).view(b, c)
        y = self.fc(y).view(b, c, 1)
        return x * y.expand_as(x)


class SEBasicBlock(nn.Module):
    expansion = 1

    def __init__(self, inplanes, planes, convType, stride=1, downsample=None, groups=1,
                 base_width=64, dilation=1, norm_layer=None,
                 *, reduction=4):
        super(SEBasicBlock, self).__init__()
        self.convType = convType
        self.conv1 = self.convType(inplanes, planes, stride)
        self.bn1 = nn.BatchNorm1d(planes)
        self.relu = nn.ReLU(inplace=True)
        self.conv2 = self.convType(planes, planes, 1)
        self.bn2 = nn.BatchNorm1d(planes)
        self.se = SELayer(planes, reduction)
        self.downsample = downsample
        self.stride = stride

    def forward(self, x):
        residual = x
        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)
        out = self.se(out)

        if self.downsample is not None:
            residual = self.downsample(x)

        out += residual
        out = self.relu(out)

        return out


class DepthwiseSeparableConv1d(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0, bias=False):
        super().__init__()
        # 深度卷积: groups=in_channels 确保每个通道独立卷积
        self.depthwise = nn.Conv1d(
            in_channels=in_channels,
            out_channels=in_channels,  # 输出通道数保持与输入相同
            kernel_size=kernel_size,
            stride=stride,
            padding=padding,
            groups=in_channels,  # 关键参数：分组数=输入通道数，实现深度卷积
            bias=bias
        )
        # 逐点卷积: 1x1卷积负责通道融合和数量变换
        self.pointwise = nn.Conv1d(
            in_channels=in_channels,
            out_channels=out_channels,
            kernel_size=1,  # 1x1卷积核
            stride=1,
            padding=0,
            bias=bias
        )
        # 通常还会添加BatchNorm和激活函数，例如：
        # self.bn = nn.BatchNorm1d(out_channels)
        # self.act = nn.ReLU()

    def forward(self, x):
        x = self.depthwise(x)
        x = self.pointwise(x)
        # x = self.bn(x)
        # x = self.act(x)
        return x


class GatedAttentionFusion(nn.Module):
    """
    门控注意力动态融合模块
    基于公式实现：z = ReLU(W_G [F_expert ⊕ F_dnn] + b_g), α = σ(W_α z + b_α), F_fusion = α·F_dnn + (1-α)·F_expert
    """

    def __init__(self, expert_dim, dnn_dim, hidden_dim=64):
        """
        初始化门控注意力融合模块

        参数:
            expert_dim: 临床医学特征的维度
            dnn_dim: 深度网络特征的维度
            hidden_dim: 中间隐藏层的维度（默认64）
        """
        super(GatedAttentionFusion, self).__init__()

        # 确保两个特征维度相同才能进行加权融合
        assert expert_dim == dnn_dim, "特征维度必须相同才能进行融合操作"
        self.feature_dim = expert_dim

        # 公式4.1的线性变换层: z = ReLU(W_G [F_expert ⊕ F_dnn] + b_g)
        self.linear_z = nn.Linear(expert_dim + dnn_dim, hidden_dim)

        # 公式4.2的线性变换层: α = σ(W_α z + b_α)
        self.linear_alpha = nn.Linear(hidden_dim, 1)

        # 激活函数
        self.relu = nn.ReLU()
        self.sigmoid = nn.Sigmoid()

    def forward(self, F_expert, F_dnn):
        """
        前向传播

        参数:
            F_expert: 临床医学特征，形状为[batch_size, expert_dim]
            F_dnn: 深度网络特征，形状为[batch_size, dnn_dim]

        返回:
            F_fusion: 融合后的特征，形状为[batch_size, feature_dim]
        """
        # 维度验证
        assert F_expert.size(-1) == self.feature_dim and F_dnn.size(-1) == self.feature_dim, \
            "输入特征维度与初始化参数不匹配"

        # 1. 特征拼接: F_expert ⊕ F_dnn
        concatenated = torch.cat([F_expert, F_dnn], dim=-1)  # 形状: [batch_size, expert_dim + dnn_dim]

        # 2. 公式4.1: z = ReLU(W_G [F_expert ⊕ F_dnn] + b_g)
        z = self.linear_z(concatenated)  # 线性变换
        z = self.relu(z)  # ReLU激活，形状: [batch_size, hidden_dim]

        # 3. 公式4.2: α = σ(W_α z + b_α)
        alpha_logits = self.linear_alpha(z)  # 线性变换，形状: [batch_size, 1]
        alpha = self.sigmoid(alpha_logits)  # Sigmoid激活，α ∈ [0, 1]，形状: [batch_size, 1]

        # 4. 公式4.3: F_fusion = α·F_dnn + (1-α)·F_expert
        F_fusion = alpha * F_dnn + (1 - alpha) * F_expert  # 形状: [batch_size, feature_dim]

        return F_fusion
